package chatNI;

import java.io.*;
import java.net.*;
import java.util.Observable;
import java.util.Observer;
import messages.*;
import chatControler.ChatControler;
import chatModel.Choix;
import chatModel.ModeleEtatNI;
import chatModel.ModeleMsgToNI;
import chatModel.UtilisateurDistant;

/**
 * ChatNI centralise la reception, l'envoi des messages et des fichiers.
 * 
 */
public class ChatNI implements Observer {

	/*
	 * Pour pouvoir lancer les 4 Threads différents
	 */
	private DatagramSocket socketUdpEmission;
	private DatagramSocket socketUdpReception;
	private ServerSocket serverTcp;
	private Socket clienttcp;
	private ThreadUdpEcoute ecoute;
	private int etat;


	/*
	 * adresse du broadcast
	 */
	private InetAddress addrBroadcast ;

	/*
	 * Patron MVC
	 */
	private ChatControler chatControler;

	/*************************************************
	 * **************** Constructeur *****************
	 ************************************************/

        /**
         * Constructeur
         * @param chatControler ChatControler du ChatSystem
         */
	public ChatNI(ChatControler chatControler) {
		super();
		this.chatControler = chatControler;
		chatControler.getMsgToNI().addObserver(this);
		chatControler.getMdlEtatNi().addObserver(this);
		this.socketUdpEmission =null;
		this.socketUdpReception =null;
		try {
			this.addrBroadcast = InetAddress.getByName("255.255.255.255");
		} catch (UnknownHostException e) {
			System.out.println("Erreur dans le InetAddress.getByName");
			e.printStackTrace();
		}

		try {
			this.socketUdpEmission = new DatagramSocket();
			this.socketUdpReception = new DatagramSocket(Port.ecouteReceptionMsg);
		} catch (SocketException e) {
			System.out.println("Erreur création de la socket");
			e.printStackTrace();
		}
	}


	/*********************
	 **----METHODES-----*
	 ********************/

	/**
         * Demarre un thread pour envoyer le message
         * @see chatNI.ThreadUdpEnvoie ThreadUdpEnvoie
         * @param myByteToSend Message binaire à envoyer
         * @param adrDistante Adresse IP à laquelle envoyer ce message
         */
        public void sendMessage(byte[] myByteToSend, InetAddress adrDistante){
		ThreadUdpEnvoie threadToSend = new ThreadUdpEnvoie(this.socketUdpEmission, myByteToSend, adrDistante);
		threadToSend.start();
	}

        /**
         * Envoi un hello pour annoncer aux autres utilisateur qu'on est connecté
         */
	public void connect(){
		try {
			System.out.println("envoie du hello");
			byte[] myHelloMsg = MessagesFactory.getHello(chatControler.getLocalUser().getUserName(), true);
			sendMessage(myHelloMsg, this.addrBroadcast);
			// On lance le thread d'écoute et on signifie au controleur qu'on est désormais connecté
			ecoute = new ThreadUdpEcoute(this.socketUdpReception, this.chatControler, true);
			ecoute.start();
		} catch (MessageCreationException e) {
			System.out.println("Probleme dans la création du message");
			e.printStackTrace();
		}
	}

        /**
         * Envoi un bye pour dire aux autres utilisateurs qu'on s'est déconnecté
         */
	public void disconnect(){
		try {
			byte[] myByeMsg = MessagesFactory.getBye(chatControler.getLocalUser().getUserName());
			sendMessage(myByeMsg, this.addrBroadcast);
			ecoute.setActif(false);
			ecoute.interrupt();
		} catch (MessageCreationException e) {
			System.out.println("Probleme dans la création du message");
			e.printStackTrace();
		}

	}

        /**
         * Lance un Thread pour recevoir un fichier
         * @see chatNI.ThreadTcpRecup ThreadTcpRecup
         * @param file Futur fichier sui sera le fichier reçu
         * @param user Utilisateur distant qui va envoyer le fichier
         * @param lengthFile Longueur du fichier
         */
	public void rcvFile(File file, UtilisateurDistant user, long lengthFile){
		try {
			serverTcp = new ServerSocket(Port.echangeFichierRecup);
			ThreadTcpRecup recupFile = new ThreadTcpRecup(serverTcp, file, this.chatControler, user, lengthFile);
			recupFile.start();
		} catch (IOException e) {
			System.out.println("probleme dans la creation du serverSocket");
			e.printStackTrace();
		}
	}

        /**
         * Lance un Thread pour envoyer un fichier
         * @see chatNI.ThreadTcpEnvoie ThreadTcpEnvoie
         * @param file Fichier à envoyer
         * @param adrDistante IP à laquelle il faut envoyer le fichier
         * @param user Utilisateur distant qui va recevoir le fichier
         */
	public void sendFile(File file, InetAddress adrDistante, UtilisateurDistant user){
		try {
			clienttcp = new Socket(adrDistante, Port.echangeFichier);
			ThreadTcpEnvoie envoieFile = new ThreadTcpEnvoie(clienttcp, file, this.chatControler, user);
			envoieFile.start();
		} catch (IOException e) {
			System.out.println("probleme dans la creation de la socket client");
			e.printStackTrace();
		}
	}

        /**
         * Redéfinition de la méthode update, gère les notifications destinées au NI
         * @see chatModel.Choix Choix
         * @param observed Modèle observé
         * @param object Objet envoyé lors de la notification (en général le modèle lui même)
         */
	@Override
	public synchronized void update(Observable observed, Object object) {
		System.out.println("update NI");
		if(observed instanceof ModeleMsgToNI ){
			ModeleMsgToNI tempUtilisateur = (ModeleMsgToNI) object;

			if(tempUtilisateur.getChoix()==Choix.connexionOk){
				System.out.println("on est dans le NI");
				this.connect();
			}
			else if(tempUtilisateur.getChoix()==Choix.rcvNewHello){
				this.sendMessage(tempUtilisateur.getMsgToSend(), tempUtilisateur.getUserDistant().getUserDistantIP());

			}

			else if(tempUtilisateur.getChoix()==Choix.sendMsgOneUser){
				System.out.println("le ni envoie un message pour un");
				byte[] myByteToSend = tempUtilisateur.getMsgToSend();
				sendMessage(myByteToSend, tempUtilisateur.getUserDistant().getUserDistantIP());
			}

			else if(tempUtilisateur.getChoix()==Choix.sendMsgAllUser){
				System.out.println("le ni envoie un message pour tous !");
				byte[] myByteToSend = tempUtilisateur.getMsgToSend();
				for(int i=0; i<chatControler.getOtherUserList().size();i++){
					if(i>0){//pour éviter qu'il envoie en broadcast
						sendMessage(myByteToSend, ((UtilisateurDistant)(chatControler.getOtherUserList().get(i))).getUserDistantIP());
					}
				}
			}

			else if(tempUtilisateur.getChoix()==Choix.deconnexionOk){
				System.out.println("le NI se deconnecte");
				this.disconnect();
			}

			else if(tempUtilisateur.getChoix()==Choix.quitChat){
				System.out.println("NI quitte");
				this.disconnect();
			}

			if(this.etat==Choix.etatConnected){

				if(tempUtilisateur.getChoix()==Choix.sendFileNotifAllUser){
					byte[] myByteToSend = tempUtilisateur.getMsgToSend();
					for(int i=0; i<chatControler.getOtherUserList().size();i++){
						if(i>0){//pour éviter qu'il envoie en broadcast
							sendMessage(myByteToSend, ((UtilisateurDistant)(chatControler.getOtherUserList().get(i))).getUserDistantIP());
						}
					}
				}

				else if(tempUtilisateur.getChoix()==Choix.sendFileNotifOneUser){
					byte[] myByteToSend = tempUtilisateur.getMsgToSend();
					sendMessage(myByteToSend, tempUtilisateur.getUserDistant().getUserDistantIP());
				}

				else if(tempUtilisateur.getChoix()==Choix.confAcceptFileOk){
					byte[] myByteToSend = tempUtilisateur.getMsgToSend();
					sendMessage(myByteToSend, tempUtilisateur.getUserDistant().getUserDistantIP());
					rcvFile(tempUtilisateur.getFile(), tempUtilisateur.getUserDistant(), tempUtilisateur.getLengthFile());
				}

				else if(tempUtilisateur.getChoix()==Choix.confAcceptFileNoOk){
					byte[] myByteToSend = tempUtilisateur.getMsgToSend();
					sendMessage(myByteToSend, tempUtilisateur.getUserDistant().getUserDistantIP());
				}

				else if(tempUtilisateur.getChoix()==Choix.rcvAcceptedFile){
					sendFile(tempUtilisateur.getFile(),tempUtilisateur.getUserDistant().getUserDistantIP(), tempUtilisateur.getUserDistant());
				}
			}
		}
		if(observed instanceof ModeleEtatNI){
			ModeleEtatNI etatActuel = (ModeleEtatNI) object;
			//Connexion ok
			if(etatActuel.getEtat()==Choix.etatConnected){
				this.etat=Choix.etatConnected;
				System.out.println("le ni est dans l'état connecté");
			}
			else if(etatActuel.getEtat()==Choix.etatRcvingFile){
				this.etat=Choix.etatRcvingFile;
				System.out.println("le ni est dans l'état reception");
			}
			else if(etatActuel.getEtat()==Choix.etatSendingFile){
				this.etat=Choix.etatSendingFile;
				System.out.println("le ni est dans l'état envoie");
			}
		}
	}
}
